//
// Created by root on 10/26/18.
//

#include <unistd.h>
#include <linux/limits.h>
#include <execinfo.h>
#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

#include "st.h"
#include "ursax.h"
#include "debug.h"
#include "log.h"
#include "utcache.h"


int debug_duration_key = -1;

int debug_numbers_key = -1;

#define MAGIC_TYPE_NORMAL (0)
#define MAGIC_TYPE_ADDR (1)
#define MAGIC_TYPE_CHUNK (2)
#define MAGIC_TYPE_HEX (3)
#define MAGIC_TYPE_STR (4)

#define DEBUG_MAX_DURATION_US (3000*1000)
#define MAX_RECORD (32*4)

struct Records{
    int nb;
    struct timeval records[MAX_RECORD];
};

uint64_t sendfile_count;
uint64_t st_write_count;
uint64_t sendfile_size;
uint64_t st_write_size;
st_thread_t worker_thread;

struct MagicNb{
    union {
        struct {
            uint32_t padding1:3;
            uint32_t port:29;
            uint32_t ip:32;
        };
        struct {
            uint32_t padding2:3;
            uint32_t volumeid:29;
            uint32_t index;
        };
        struct {
            uint8_t padding;
            char string[7];
        };
        struct {
            uint64_t type:3;
            uint64_t nb:61;
        };
        uint64_t value;
    };
};

struct RecordNbs{
    int nb;
    struct MagicNb records[MAX_RECORD];
};

int format_magic_nb(char* buf, uint64_t _nb)
{
    struct MagicNb _, *nb = &_;
    nb->value = _nb;
    switch(nb->type){
        case MAGIC_TYPE_NORMAL:
            return sprintf(buf, "%ld ", (uint64_t)nb->nb);
        case MAGIC_TYPE_ADDR:
            return sprintf(buf, "%s:%d ", str_ip(nb->ip), nb->port);
        case MAGIC_TYPE_CHUNK:
            return sprintf(buf, "%08x.%d ", nb->volumeid, nb->index);
        case MAGIC_TYPE_HEX:
            return sprintf(buf, "%lx ", (uint64_t)nb->nb);
        case MAGIC_TYPE_STR:
            return sprintf(buf, "%s ", nb->string);
        default:
            return sprintf(buf, "%lx ", _nb);
    }
    return 0;
}

char* get_thread_magics()
{
    static char magics_strs[4096*4];
    if(debug_numbers_key < 0){
        return magics_strs;
    }
    int len = 0;
    struct RecordNbs *r = (struct RecordNbs *)st_thread_getspecific(debug_numbers_key);
    if(!r){
        return magics_strs;
    }
    for(int i = 0; i < r->nb; i++){
        len += format_magic_nb(magics_strs+len, r->records[i].value);
    }
    magics_strs[len-1] = '\0';
    return magics_strs;
}

void wrap_print_call_stack(bool *in_print_call_stack)
{
    if(*in_print_call_stack) {
        return;
    }
    *in_print_call_stack = true;
    print_call_stack();
    *in_print_call_stack = false;
}

int print_call_stack()
{
    return 0;
}

int debug_reference(int ref)
{
    LOG_INFO("%s ref %d", __func__, ref);
    print_call_stack();
    return 0;
}

void PUSH_THREAD_MAGIC(uint64_t nb)
{
    if(debug_numbers_key < 0) {
        return;
    }
    struct RecordNbs *r = (struct RecordNbs *)st_thread_getspecific(debug_numbers_key);
    if(!r) {
        r = (struct RecordNbs *)zalloc(sizeof(struct RecordNbs));
        st_thread_setspecific(debug_numbers_key, r);
    }
    if(r->nb >= MAX_RECORD) {
        LOG_ERROR("error r.nb %d", r->nb);
        return;
    }
    r->records[r->nb++].value = nb;
}

void PUSH_THREAD_CHUNK_ID(uint32_t volumeid, uint32_t index)
{
    LOG_DEBUG("%08x:%d", volumeid, index);
    struct MagicNb nb;
    nb.type = MAGIC_TYPE_CHUNK;
    nb.volumeid = volumeid;
    nb.index = index;
    PUSH_THREAD_MAGIC(nb.value);
}

void PUSH_THREAD_ADDR(uint32_t ip, uint16_t port)
{
    LOG_DEBUG("%s:%d", str_ip(ip), port);
    struct MagicNb nb;
    nb.type = MAGIC_TYPE_ADDR;
    nb.ip = ip;
    nb.port = port;
    PUSH_THREAD_MAGIC(nb.value);
}

void POP_THREAD_MAGIC()
{
    if(debug_numbers_key < 0) {
        return;
    }

    struct RecordNbs *r = (struct RecordNbs *)st_thread_getspecific(debug_numbers_key);
    if(!r){
        LOG_ERROR("error r is NULL");
        return;
    }
    if(r->nb == 0) {
        LOG_ERROR("error r.nb %d", r->nb);
        return;
    }
    r->nb--;
}

void POP_THREAD_MAGIC_n(int n)
{
    for(int i = 0; i < n; i++) {
        POP_THREAD_MAGIC();
    }
}

int st_backtrace(st_thread_t th, void **array, int size, void** rbp, void** top)
{
    return 0;

//    if(!th && !rbp){
//        return 0;
//    }
//    if(!rbp){
//        rbp = (void**)th->context[0].__jmpbuf[1];
//    }
//    if(!top){
//        top = rbp;
//    }
//    if(th && th->stack){
//        top = (void**)th->stack->stk_top;
//    }else{
//        LOG_INFO("th %p not have st stack, must be main thread rbp is %p top is %p, sizeof thread is %d %p",
//                 th, rbp, top, sizeof(*th), rbp);
//    }
//
//    return get_backtrace(rbp, top, array, size);
}

extern int log_fd;
static int print_functions(void* buffer[], int nptrs)
{
    LOG_INFO("functions:");
    // char** strings = backtrace_symbols(buffer, nptrs);
    char **strings = NULL;
    Dl_info dlip;
    for (int j = 0; j < nptrs; j++) {
        int ret = dladdr(buffer[j], &dlip);
        char buf_write[4096];
        const char *func_name = NULL;
        if(ret != 0){
            func_name = dlip.dli_sname;
        }else if(strings && strings[j]){
            func_name = strchr(strings[j], '(');
        }
        // int len = snprintf(buf_write, sizeof(buf_write)-1, "call_stack: %s in '%s' line %u\n", func_name, file, line);
        int len = snprintf(buf_write, sizeof(buf_write)-1, "call_stack: %s()\n", func_name);
        buf_write[len] = '\0';
        write(log_fd, buf_write, len);
    }

    // free(strings);
    return 0;
}

int st_print_call_stack(void *st_thread)
{
    LOG_DEBUG("st_print_call_stack ");
    void* buffer[128];
#ifndef NATIVE_SOCKET
    int nptrs = st_backtrace((st_thread_t)st_thread, buffer, LEN(buffer), NULL, NULL);
#else
    int nptrs = backtrace(buffer, LEN(buffer));
#endif

    return print_functions(buffer, nptrs);
}

static int checker_callback(uint64_t key, void* data, void* arg)
{
    void* th = (void*)key;
    int now = time(0);
    LOG_INFO("checker callback %p %d %d", th, now, (int)(long)data);
    st_print_call_stack(th);
    return 0;
}

static void free_data(void* data){}
struct foo_cache* thread_tracker_table;

void init_debug_thread()
{
    thread_tracker_table = foo_cache_create(10000, &free_data, false);
}

void show_dead_thread()
{
    foo_cache_item_iterate(thread_tracker_table, &checker_callback, NULL);
}

void fini_debug_thread()
{
    if(!thread_tracker_table){
        return;
    }
    foo_cache_delete(thread_tracker_table, 0);
    thread_tracker_table = NULL;
}

static inline uint64_t usec_diff(struct timeval *tm1, struct timeval *tm2)
{
    return 1000 * 1000 * (tm2->tv_sec - tm1->tv_sec) + (tm2->tv_usec - tm1->tv_usec);
}

void START_RECORD()
{
#ifndef DEBUG_DURATION
    return;
#endif

    struct Records *r = (struct Records *)st_thread_getspecific(debug_duration_key);
    if(!r){
        r = (struct Records *)zalloc(sizeof(struct Records));
        st_thread_setspecific(debug_duration_key, r);
    }
    if(r->nb >= MAX_RECORD){
        LOG_ERROR("error r.nb %d", r->nb);
        return;
    }
    gettimeofday(&r->records[r->nb++], NULL);
}

void END_RECORD()
{
#ifndef DEBUG_DURATION
    return;
#endif

    struct Records *r = (struct Records *)st_thread_getspecific(debug_duration_key);
    if(r->nb == 0){
        LOG_ERROR("error r.nb %d", r->nb);
        return;
    }
    struct timeval *record = &r->records[--r->nb];
    struct timeval now;
    gettimeofday(&now, NULL);
    int diff = usec_diff(record, &now);
    if(diff > DEBUG_MAX_DURATION_US){
        LOG_INFO("duration max than %d us (%d), check about", DEBUG_MAX_DURATION_US, diff);
        print_call_stack();
    }
}